﻿// Inner Fire 游戏引擎库
// DirectX 基础工具类
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 把DxUtils分离出来，不要每次core改动都编译。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-13

#include "dx_utils.h"
#include "DDSTextureLoader.h"
#include "shader_cache.h"
#include <D3Dcompiler.h>
#include <DirectXCollision.h>
#include <DirectXColors.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <EASTL/vector.h>
#include <Unknwn.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <codecvt>
#include <comdef.h>
#include <cstdint>
#include <dx/core/dx_core.h>
#include <dx/core/dx_exception.h>
#include <fstream>
#include <klog.h>
#include <kstring.h>
#include <locale>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <windows.h>
#include <wrl.h>

namespace ifire::dx {

#pragma region DxUtils

bool DxUtils::msaa_enable_ = false; // 4X MSAA 是否启用
UINT DxUtils::msaa_quality_ = 0;    // 4X MSAA 质量级别

void DxUtils::CheckMsaa(ID3D12Device* device) {
  D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS quality_levels = {};
  quality_levels.Format = BACK_BUFFER_FORMAT;
  quality_levels.SampleCount = 4;
  quality_levels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
  quality_levels.NumQualityLevels = 0;
  HASSERT(device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
      &quality_levels, sizeof(quality_levels)));
  // 得到项目
  msaa_quality_ = quality_levels.NumQualityLevels;
  assert(msaa_quality_ > 0 && "Unexpected MASS quality level.");

  // KLOG("4xMASS quality: %d", msaa_quality_);
}

ComPtr<ID3DBlob> DxUtils::LoadShader(const std::string& filename) {
  std::ifstream fin(filename, std::ios::binary);

  // 确定文件的大小
  fin.seekg(0, std::ios_base::end);
  std::ifstream::pos_type size = (int)fin.tellg();
  fin.seekg(0, std::ios_base::beg);

  // 创建Blob
  ComPtr<ID3DBlob> blob;
  D3DCreateBlob(size, blob.GetAddressOf());

  // 载入到blob中
  fin.read((char*)blob->GetBufferPointer(), size);
  fin.close();

  return blob;
}

ComPtr<ID3DBlob> DxUtils::CompileOrLoadShader(const std::string& filename,
    const D3D_SHADER_MACRO* defines, const std::string& entry_point,
    const std::string& target) {

  // 初始化缓存系统(只需调用一次)
  static bool initialized = false;
  if (!initialized) {
    ShaderCache::Initialize(L"shader_cache");
    initialized = true;
  }

  // 使用缓存系统
  return ShaderCache::CompileOrLoad(filename, defines, entry_point, target);
}

ComPtr<ID3DBlob> DxUtils::CompileShader(const std::string& filename,
    const D3D_SHADER_MACRO* defines, const std::string& entry_point,
    const std::string& target) {
  // 如果处于调式模式，则使用调式根据
  UINT compile_flags = 0;
#if defined(DEBUG) || defined(_DEBUG)
  compile_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

  // 转化为宽字符
  auto filename_w = ikit::to_wide(filename);

  // 编译Shader
  ComPtr<ID3DBlob> byte_code = nullptr;
  ComPtr<ID3DBlob> errors;
  HRESULT hr = D3DCompileFromFile(filename_w.c_str(), defines,
      D3D_COMPILE_STANDARD_FILE_INCLUDE, entry_point.c_str(), target.c_str(),
      compile_flags, 0, &byte_code, &errors);

  // 如果有错误则输出
  if (errors != nullptr) {
    ikit::log((char*)errors->GetBufferPointer());
  }
  // 常规异常处理
  HASSERT(hr);

  return byte_code;
}

static std::function<void(std::string)> output_function;
void DxUtils::SetOutputStream(std::function<void(std::string)> func) {
  output_function = func;
}

void DxUtils::Log(const std::string& msg) {
  if (output_function) {
    output_function(msg);
  } else {
    OutputDebugStringA(msg.c_str());
  }
}

std::uint64_t current_id_ = 1000000;
std::mutex mutex_;

std::uint64_t DxUtils::ObtainInstancedId() {
  std::lock_guard<std::mutex> lock(mutex_);
  return ++current_id_;
}

bool DxUtils::IsKeyDown(int vkeyCode) {
  return (GetAsyncKeyState(vkeyCode) & 0x8000) != 0;
}

ComPtr<ID3D12Resource> DxUtils::CreateDefaultBuffer(ID3D12Device* device_,
    ID3D12GraphicsCommandList* cmd_list_, const void* data, UINT64 data_size,
    ComPtr<ID3D12Resource>& upload_buffer) {
  // 缓冲区指针
  ComPtr<ID3D12Resource> buffer;

  // 创建实际的默认缓冲区资源
  CD3DX12_HEAP_PROPERTIES heap_props(D3D12_HEAP_TYPE_DEFAULT);
  auto buffer_desc = CD3DX12_RESOURCE_DESC::Buffer(data_size);
  HASSERT(device_->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE,
      &buffer_desc, D3D12_RESOURCE_STATE_COMMON, nullptr,
      IID_PPV_ARGS(buffer.GetAddressOf())));

  // 为了将CPU端内存的数据复制到默认缓冲区，需要一个处于中介位置的上传堆
  CD3DX12_HEAP_PROPERTIES upload_props(D3D12_HEAP_TYPE_UPLOAD);
  auto upload_buffer_desc = CD3DX12_RESOURCE_DESC::Buffer(data_size);
  HASSERT(device_->CreateCommittedResource(&upload_props, D3D12_HEAP_FLAG_NONE,
      &upload_buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
      IID_PPV_ARGS(upload_buffer.GetAddressOf())));

  // 描述要复制到默认缓冲区的数据
  D3D12_SUBRESOURCE_DATA subresource_data = {};
  subresource_data.pData = data;
  subresource_data.RowPitch = data_size;
  subresource_data.SlicePitch = subresource_data.RowPitch;

  // 将数据复制到默认缓冲区的流程如下：
  // 1：UpdateSubresources将数据从CPU内存复制到中介的上传堆
  // 2：通过CopySubresourceRegion把上传堆的数据复制到buffer中。

  // 使用资源屏障将buffer从COMMON(常规)设置为COPY_DEST(复制目标)
  auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(buffer.Get(),
      D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
  cmd_list_->ResourceBarrier(1, &barrier);
  // 上传资源到Uploader。
  // 这里我有一点以前没搞明白的。按理来说，这条执行之后，Uploader中就已经存在了数据。
  // 原数据就可以删除了，只是Uploader这个中介不能删除而已。因为教程中用了eastl::vector，
  // 在函数进行完就会被释放的。
  UpdateSubresources<1>(
      cmd_list_, buffer.Get(), upload_buffer.Get(), 0, 0, 1, &subresource_data);
  // 使用资源屏障将buffer从COPY_DEST(复制目标)转为GENERIC_READ（可读用）
  auto barrier2 = CD3DX12_RESOURCE_BARRIER::Transition(buffer.Get(),
      D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_GENERIC_READ);
  cmd_list_->ResourceBarrier(1, &barrier2);

  // 注意，在调用上述函数之后，必须保存load_buffer依然存在，而不能对它立刻进行销毁。
  // 因为命令列表中的复制操作可能还没有执行。当调用者知道复制已完成才能释放upload_buffer。

  return buffer;
}

#pragma endregion
} // namespace ifire::dx